﻿using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace Kyoh.Lib.Component
{
    /// <summary>オブジェクトに対して、指定されたプロパティによるオブジェクトの比較を行います。</summary>
    /// <typeparam name="T">比較するオブジェクトの型。</typeparam>
    public class PropertyComparer<T> : IComparer<T>, IEqualityComparer<T>
    {
        private readonly PropertyDescriptor _property;
        private readonly ListSortDirection _direction;

        /// <summary><see cref="PropertyComparer{T}"/> クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="property">比較対象とするプロパティを表す <see cref="PropertyDescriptor"/> オブジェクト。</param>
        /// <param name="direction">ソート時の昇順・降順を表す <see cref="ListSortDirection"/> 列挙値。</param>
        public PropertyComparer(PropertyDescriptor property, ListSortDirection direction)
        {
            _property = property;
            _direction = direction;
        }

        #region IComparer<T> メンバー

        /// <summary>オブジェクトをプロパティの値により比較します。</summary>
        /// <param name="obj1">比較対象の1つめのオブジェクト。</param>
        /// <param name="obj2">比較対象の2つめのオブジェクト。</param>
        /// <returns>比較結果。ソート時の順序が降順の場合は、値が-1倍されます。</returns>
        public int Compare(T obj1, T obj2)
        {
            var val1 = GetPropertyValue(obj1, _property.Name);
            var val2 = GetPropertyValue(obj2, _property.Name);

            return _direction == ListSortDirection.Ascending
                       ? CompareCore(val1, val2)
                       : CompareCore(val1, val2)*-1;
        }

        #endregion

        /// <summary>オブジェクトをプロパティの値により、等値比較します。</summary>
        /// <param name="obj1">比較対象の1つめのオブジェクト。</param>
        /// <param name="obj2">比較対象の2つめのオブジェクト。</param>
        /// <returns>比較結果。2つのオブジェクトのプロパティが等しい場合は <c>true</c>。それ以外の場合は <c>false</c>。</returns>
        public bool Equals(T obj1, T obj2)
        {
            return object.Equals(obj1, obj2)
                   || (!Equals(obj1, null)
                       && !Equals(obj2, null)
                       &&
                       CompareCore(GetPropertyValue(obj1, _property.Name), GetPropertyValue(obj2, _property.Name)) == 0);
        }

        /// <summary>オブジェクトのハッシュコードを取得します。</summary>
        /// <param name="obj">ハッシュコードを取得するオブジェクト。</param>
        /// <returns>オブジェクトのハッシュコード。</returns>
        public int GetHashCode(T obj)
        {
            return 0;
        }

        private int CompareCore(object value1, object value2)
        {
            if (Equals(value1, null))
                return Equals(value2, null) ? 0 : -1;
            if (Equals(value2, null))
                return 1;
            if (value1.Equals(value2))
                return 0;
            var comparable = value1 as IComparable;
            if (comparable != null)
                return (comparable).CompareTo(value2);
            return string.CompareOrdinal(value1.ToString(), value2.ToString());
        }

        private static object GetPropertyValue(T obj, string property)
        {
            if (Equals(obj, null))
                return null;

            var propertyInfo = obj.GetType().GetProperty(property);
            return propertyInfo.GetValue(obj, null);
        }
    }
}